package threads;

import java.security.PublicKey;

import javax.swing.JOptionPane;

import messages.CompetenceValidationRequestMessage;
import messages.CompetenceValidationResponseMessage;
import messages.DocumentSigningRequestMessage;
import messages.DocumentSigningResponseMessage;
import messages.NonceToSignRequestMessage;
import messages.NonceToSignResponseMessage;
import messages.SignatureValidationRequestMessage;
import messages.SignatureValidationResponseMessage;
import security.SecureRandomNumber;
import server.ServerData;
import utils.SignatureUtils;
import connection.TCPSecureServerSocket;
import connection.exception.ConnectionResetException;

class CertcopClientThread extends Thread{

	private TCPSecureServerSocket _socket;
	private ServerData _serverData;

	CertcopClientThread(TCPSecureServerSocket socket){
		_socket = socket;
		_serverData = ServerData.getServerData();
	}

	public void run(){
		try {
			for(int numTries = 0; numTries < 3; numTries++){
				_socket.waitChannelEstablishmentMode();
				if(_socket.isSecureChannelEstablished())
					break;
			}
			
			if(!_socket.isSecureChannelEstablished()){
				JOptionPane.showMessageDialog(null, "Impossível estabelecer canal seguro com o cliente.");
			}

			while(_serverData.isOnline() || _socket.isSecureChannelEstablished()){
				Object msg = _socket.receive();

				if(msg == null)
					continue;
				
				if(msg.getClass().getName().equals("messages.NonceToSignRequestMessage")) {					
					processNonceToSignRequestMessage(msg);					
				}else if(msg.getClass().getName().equals("messages.CompetenceValidationRequestMessage")) {					
					processCompetenceValidationRequestMessage(msg);
				}else if(msg.getClass().getName().equals("messages.DocumentSigningRequestMessage")) {
					processDocumentSigningRequestMessage(msg);
				}else if(msg.getClass().getName().equals("messages.SignatureValidationRequestMessage")) {
					processSignatureValidationRequestMessage(msg);
				} else {
					throw new RuntimeException("Received an Unsupported Message Type: " + msg.getClass().getName());
				}
			}

		} catch (ConnectionResetException e) {
			System.out.println("Ligação com o cliente terminada.");
			_socket.close();
			return;
		}
	}
	
	private void processNonceToSignRequestMessage(Object msg) throws ConnectionResetException{
		// Fazer Cast à mensagem
		NonceToSignRequestMessage req = (NonceToSignRequestMessage) msg;

		// Obter nonce para devolver
		Integer nonce = SecureRandomNumber.generateNonce();

		// Registar nonce Localmente
		String fName = req._firstName;
		String lName = req._lastName;
		String comp = req._competence;
		_serverData.addNonce(nonce, fName, lName, comp);

		// Enviar Resposta
		NonceToSignResponseMessage res = new NonceToSignResponseMessage(nonce);
		_socket.send(res);
	}
	
	private void processCompetenceValidationRequestMessage(Object msg) throws ConnectionResetException{
		// Fazer Cast à mensagem
		CompetenceValidationRequestMessage req = (CompetenceValidationRequestMessage) msg;

		// Verificação de Autenticação
		String sig =  req._signature;
		Integer nonce = req._nonce;
		Boolean authResult = _serverData.isCompetenceSignatureValid(sig, nonce);

		// Verificação de Competência
		String[] name = _serverData.getUserByNonce(nonce);
		String firstName = name[0];
		String lastName = name[1];
		String competency = req._competence;
		Boolean result = _serverData.userHasCompetency(firstName, lastName, competency);

		// Remover nonce (já não é necessário) e enviar Resposta
		_serverData.removeNonce(nonce);
		CompetenceValidationResponseMessage res = new CompetenceValidationResponseMessage(result && authResult);
		//				    CompetenceValidationResponseMessage res = new CompetenceValidationResponseMessage(authResult);
		_socket.send(res);
	}
	
	private void processDocumentSigningRequestMessage(Object msg) throws ConnectionResetException{
		// Fazer Cast à mensagem
		DocumentSigningRequestMessage req = (DocumentSigningRequestMessage) msg;

		// Verificação de Autenticação
		String sig =  req._nonceSignature;
		Integer nonce = req._nonce;
		Boolean authResult = _serverData.isCompetenceSignatureValid(sig, nonce);

		// Obter dados para assinatura, caso necessários
		String[] name = ServerData.getServerData().getUserByNonce(nonce);
		String firstName = name[0];
		String lastName = name[1];
		String competence = req._competence;

		// Remover nonce (já não é necessário)
		_serverData.removeNonce(nonce);

		// Se a verificação falhar, enviar mensagem de erro vazia, e termina
		if(!authResult) {
			DocumentSigningResponseMessage res = 
				new DocumentSigningResponseMessage(false, "", "");
			_socket.send(res);
			return;
		}

		// Obter Documento a Assinar
		String file = req._file;

		// Obter se é uma assinatura "VERICOP"
		Boolean isVericop = req._hasPriority;

		// Obter duração
		Integer duration = req._duration;

		// Obter Certificado e Assinatura, do Documento
		String[] sigAndCert = _serverData.generateSignature(file, firstName + " " + lastName, competence, isVericop, duration);
		String certStruct = sigAndCert[0];
		String certSignature = sigAndCert[1];

		// Enviar Resposta
		DocumentSigningResponseMessage res = 
			new DocumentSigningResponseMessage(true, certStruct, certSignature);
		_socket.send(res);
	}
	
	private void processSignatureValidationRequestMessage(Object msg) throws ConnectionResetException{
		// Fazer Cast à mensagem
		SignatureValidationRequestMessage req = (SignatureValidationRequestMessage) msg;                    

		// Obter o serial, o documento, a estrutura da assinatura e a sua assinatura
		String document = req._document;
		String signatureStruct = req._signatureStruct;
		Integer serial = new Integer(SignatureUtils.getSignatureFieldFromString(signatureStruct, 1));
		String sigReceived = SignatureUtils.getSignatureFieldFromString(signatureStruct, 8);

		// Verificação Local da Assinatura
		PublicKey pubKey = _serverData.getKeystoreManager().getServerPublicKey(true);
		Boolean result = SignatureUtils.validateStringSignatureStructure(document, signatureStruct, pubKey);

		// Determinar se é necessária verificação VERICOP
		Boolean isVericop = new Boolean(SignatureUtils.getSignatureFieldFromString(signatureStruct, 5));
		Boolean vResult = true;
		if(isVericop) {
			// Verificação da Assinatura VERICOP 
			vResult =  _serverData.isVericopSignatureValid(sigReceived, serial);
		}
		
		// Enviar Resposta
		SignatureValidationResponseMessage res = 
			new SignatureValidationResponseMessage(serial, result && vResult);
		_socket.send(res);
	}
}
